﻿using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Options;
using MongoDB.Bson.Serialization.Serializers;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Sustainalytics.Entities.ESGRatings;
using Sustainalytics.ESG.Entities;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Sustainalytics.ClientDW.DataAccessLayer.ESGRatings
{
    public class WeightMatricesRepository : IWeightMatricesRepository
    {
        private static object _syncLock = new object();
        private IMongoDatabase _database;
        private string _databaseName;

        private WeightMatricesRepository(string connectionString)
        {
            lock (_syncLock)
            {
                if (!BsonClassMap<CompanyWeights>.IsClassMapRegistered(typeof(CompanyWeights)))
                {
                    var dic = new DictionaryInterfaceImplementerSerializer<Dictionary<string, double>>(DictionaryRepresentation.ArrayOfArrays);
                    var dic2 = dic.WithValueSerializer(new DoubleSerializer());
                    BsonClassMap.RegisterClassMap<CompanyWeights>(cm =>
                    {
                        cm.AutoMap();
                        cm.MapMember(c => c.Weights).SetSerializer(dic2);
                    });
                    BsonClassMap.RegisterClassMap<PeerGroupWeights>(cm =>
                    {
                        cm.AutoMap();
                        cm.MapMember(c => c.Weights).SetSerializer(dic2);
                    });
                }
            }

            var dbConnectionString = MongoUrl.Create(connectionString);

            if (string.IsNullOrWhiteSpace(dbConnectionString.DatabaseName))
            {
                throw new ArgumentException("No database specified in the connection string!");
            }

            var client = new MongoClient(dbConnectionString);
            _database = client.GetDatabase(dbConnectionString.DatabaseName);
            _databaseName = dbConnectionString.DatabaseName;
        }

        public static IWeightMatricesRepository GetInstance(string connectionString)
        {
            return new WeightMatricesRepository(connectionString);
        }

        public void Cleanup()
        {
            _database.Client.DropDatabase(_databaseName);
        }

        public WeightMatrix GetWeightMatrix(Guid weightMatrixId)
        {
            return _database.GetCollection<WeightMatrix>(typeof(WeightMatrix).Name)
                            .AsQueryable()
                            .SingleOrDefault(wm => wm.Id == weightMatrixId);
        }

        public WeightMatrix GetWeightMatrixByNameAndAccountId(string weightMatrixName, Guid? accountId = null)
        {
            if (string.IsNullOrWhiteSpace(weightMatrixName))
            {
                throw new ArgumentException("No weight matrix name provided for searching!");
            }

            return _database.GetCollection<WeightMatrix>(typeof(WeightMatrix).Name)
                            .AsQueryable()
                            .Where(wm => wm.Name.ToLowerInvariant() == weightMatrixName.ToLowerInvariant() && wm.AccountId == accountId)
                            .SingleOrDefault();
        }

        public void SaveWeightMatrix(WeightMatrix weightMatrix)
        {
            if (weightMatrix == null)
            {
                throw new ArgumentException("No weight matrix provided for saving!");
            }

            _database.GetCollection<WeightMatrix>(typeof(WeightMatrix).Name).ReplaceOne(x => x.Id == weightMatrix.Id, weightMatrix, new UpdateOptions { IsUpsert = true });
        }

        public void DeleteWeightMatrix(Guid weightMatrixId)
        {
            _database.GetCollection<WeightMatrix>(typeof(WeightMatrix).Name)
                .DeleteMany(wm => wm.Id == weightMatrixId);
        }

        public void DeletePeerGroupsWeights(Guid weightMatrixId)
        {
            _database.GetCollection<PeerGroupWeights>(typeof(PeerGroupWeights).Name)
                .DeleteMany(pgw => pgw.MatrixId == weightMatrixId);
        }

        public void DeleteCompaniesWeights(Guid weightMatrixId)
        {
            _database.GetCollection<CompanyWeights>(typeof(CompanyWeights).Name)
                .DeleteMany(x=> x.MatrixId == weightMatrixId);
        }

        public void SavePeerGroupWeights(IEnumerable<PeerGroupWeights> peerGroupWeights)
        {
            if (peerGroupWeights == null)
            {
                throw new ArgumentException("No peer group weights provided for saving!");
            }

            if (peerGroupWeights.Any())
            {
                _database.GetCollection<PeerGroupWeights>(typeof(PeerGroupWeights).Name).InsertMany(peerGroupWeights);
            }
        }

        public void SaveCompanyWeights(IEnumerable<CompanyWeights> companyWeights)
        {
            if (companyWeights == null)
            {
                throw new ArgumentException("No company weights provided for saving!");
            }

            if (companyWeights.Any())
            {
                _database.GetCollection<CompanyWeights>(typeof(CompanyWeights).Name).InsertMany(companyWeights);
            }
        }

        public List<WeightMatrix> ListWeightMatrices()
        {
            return _database.GetCollection<WeightMatrix>(typeof(WeightMatrix).Name).AsQueryable().ToList();
        }

        public List<WeightMatrix> ListWeightMatricesByAccountId(Guid? accountId, bool includeDetails = false)
        {
            if (!includeDetails)
            {
                var query = Builders<WeightMatrix>.Filter.Where(a => a.AccountId == accountId);
                return _database.GetCollection<WeightMatrix>(typeof(WeightMatrix).Name)
                                .Find(query)
                                .ToList();
            }
            return _database.GetCollection<WeightMatrix>(typeof(WeightMatrix).Name)
                        .AsQueryable()
                        .Where(wm => wm.AccountId == accountId)
                        .ToList();
        }

        public int CountWeightMatricesByAccountId(Guid? accountId)
        {
            return _database
                            .GetCollection<WeightMatrix>(typeof(WeightMatrix).Name)
                            .AsQueryable()
                            .Count(wm => wm.AccountId == accountId);
        }

        public Dictionary<Guid, List<PeerGroupWeights>> ListPeerGroupWeights(IEnumerable<Guid> weightMatricesIds)
        {
            if (weightMatricesIds == null)
            {
                throw new ArgumentException("No weight matrices IDs provided!");
            }

            return _database.GetCollection<PeerGroupWeights>(typeof(PeerGroupWeights).Name)
                            .AsQueryable()
                            .Where(pgw => weightMatricesIds.Contains(pgw.MatrixId))
                            .ToList()
                            .GroupBy(pgw => pgw.MatrixId)
                            .ToDictionary(pgwg => pgwg.Key, pgwg => pgwg.ToList());
        }

        public Dictionary<Guid, List<CompanyWeights>> ListCompanyWeights(IEnumerable<Guid> weightMatricesIds)
        {
            if (weightMatricesIds == null)
            {
                throw new ArgumentException("No weight matrices IDs provided!");
            }

            return _database.GetCollection<CompanyWeights>(typeof(CompanyWeights).Name)
                            .AsQueryable()
                            .Where(pgw => weightMatricesIds.Contains(pgw.MatrixId))
                            .ToList()
                            .GroupBy(pgw => pgw.MatrixId)
                            .ToDictionary(pgwg => pgwg.Key, pgwg => pgwg.ToList());
        }

        public List<CompanyWeights> ListCompanyWeightsByCompanyIds(IEnumerable<Guid> companyIds)
        {
            if (companyIds == null)
                throw new ArgumentException("companyIds");

            return _database.GetCollection<CompanyWeights>(typeof(CompanyWeights).Name)
                            .AsQueryable()
                            .Where(pgw => companyIds.Contains(pgw.CompanyId))
                            .ToList();
        }



        public void RemoveCompanyWeightsByCompanyIds(IEnumerable<Guid> companyIds)
        {
            _database.GetCollection<CompanyWeights>(typeof(CompanyWeights).Name).DeleteMany(Builders<CompanyWeights>.Filter.In(x=> x.CompanyId, companyIds));
        }

        public void DeletePeerGroupsWeights(IEnumerable<PeerGroupWeights> weights)
        {
            var selectedWeights = weights.Select(x => new { x.MatrixId, x.PeerGroupName });

            foreach (var weight in selectedWeights)
                _database.GetCollection<PeerGroupWeights>(typeof(PeerGroupWeights).Name)
                    .DeleteMany(Builders<PeerGroupWeights>.Filter.And(
                        Builders<PeerGroupWeights>.Filter.Eq(x => x.PeerGroupName, weight.PeerGroupName),
                        Builders<PeerGroupWeights>.Filter.Eq(x => x.MatrixId, weight.MatrixId))
                    );
        }

        public void DeleteCompaniesWeights(IEnumerable<CompanyWeights> weights)
        {
            var selectedWeights = weights.GroupBy(x => x.MatrixId,x=>x.CompanyId);
            var collection = _database.GetCollection<CompanyWeights>(typeof(CompanyWeights).Name);

            foreach (var grouping in selectedWeights)
            {
                collection.DeleteMany(x => x.MatrixId == grouping.Key && grouping.Contains(x.CompanyId));
            }
        }

    }
}
